import RampartController from '@/modules/structures/rampart'
import { RoomDangerAreaType, RoomDangerAreaWeight } from '@/modules/structures/rampart/types'
import SpawnController, { getCreeps } from '@/modules/structures/spawn'
import { RoomSafeState } from '@/room/state/types'
import { DefenderType, DefenseMemory, EnemyGroup } from './types'
import { CreepRoles, SpawnTask } from '@/modules/structures/spawn/types'
import { ZipPos } from '@/modules/structureCache'
import { getCreepWarAbility } from '@/utils/war'
import { findClosestObjByXY, findClosestPosByXY } from '@/utils/position'
import { globalAttack } from '../../war/squad/squadWork'
import { goTo } from '@/modules/move'
import TowerController from '@/modules/structures/tower'
import { BodyPartArrayType } from '@/utils/common'
import { defineGlobalProperty } from '@/ayaka'

/**
 * 防御任务管理器
 */
export default class DefenseTaskManager {
    /**
     * 房间中空闲的边缘 rampart 的位置
     */
    protected static freeRampart: {
        [roomName: string]: {
            time: number
            pos: RoomPosition[]
        }
    } = {}

    /**
     * 房间中的核弹
     */
    public static nukes: {
        [roomName: string]: {
            id: Id<Nuke>
            landTime: number
            pos: RoomPosition
        }[]
    } = {}

    /**
     * 房间中敌人的分组
     */
    protected static enemyGroup: {
        [roomName: string]: EnemyGroup[]
    } = {}

    /**
     * 运行
     */
    public static run(room: MyRoom) {
        DefenseTaskManager.updateNuke(room)

        // 有安全模式就不防御了，或者无终端
        if (room.controller.safeMode || !room.terminal) return

        const memory = DefenseTaskManager.getMemory(room)
        const defenders = DefenseTaskManager.getDefender(room, memory.defender)
        if (room.memory.state.safe?.state !== RoomSafeState.underAttack && !defenders.length) return

        if (defenders.length) {
            // 测试用
            if (!Game.flags[`pauseattack_${room.name}`]) {
                globalAttack(room)
            }
        }

        const visual = new RoomVisual(room.name)
        // 敌人分组
        const enemyGroups = DefenseTaskManager.getEnemyGroup(room, defenders, memory)
        // 敌人名字映射
        const enemyNameMap = new Map<string, Creep>(room.enemys?.map((creep) => [creep.name, creep]))
        // 已经被占用的 rampart 点位
        const usedPosSet = new Set<ZipPos>()
        // 塔攻击目标
        const towerTargets: Creep[] = []

        enemyGroups.forEach((group) => {
            const groupEnemys = group.enemyNames.map((name) => enemyNameMap.get(name)).filter(Boolean) as Creep[]
            if (!groupEnemys.length) return
            const groupDefenders = group.defenders.map((name) => Game.creeps[name]).filter(Boolean)
            if (!groupDefenders.length) return

            // 该分组的中心点
            const groupCenter = DefenseTaskManager.calcEnemyGroupCenter(groupEnemys)
            groupDefenders.forEach((defender) => {
                // 可视化防御爬争对的敌人分组
                visual.line(defender.pos.x, defender.pos.y, groupCenter.x, groupCenter.y, {
                    color: 'blue',
                })

                if (defender.hits < defender.hitsMax) {
                    TowerController.allHeal(room.tower, defender)
                }

                // 先 boost
                if (defender.memory.boost && !defender.memory.boost.done) {
                    if (!room.enemys?.length) {
                        defender.memory.boost.wait = true
                    } else {
                        delete defender.memory.boost.wait
                    }
                    return
                }

                const defenderType = defender.memory.taskId as DefenderType
                let pos: RoomPosition | undefined
                if (defenderType === 'red' || defenderType === 'blue') {
                    // 找离敌人最近的 rampart 点位
                    pos = DefenseTaskManager.getNearestFreeRampart(room, usedPosSet, groupCenter)
                    if (!pos) return
                    DefenseTaskManager.defenderMove(defender, pos)
                } else if (defenderType === 'red_green') {
                    const closestEnemy = defender.pos.findClosestByRange(groupEnemys)
                    if (!closestEnemy) {
                        const centerPos = new RoomPosition(room.memory.center[0], room.memory.center[1], room.name)
                        if (defender.pos.getRangeTo(centerPos) > 5) {
                            goTo(defender, centerPos, { range: 5 })
                        }
                        return
                    }

                    const range = defender.pos.getRangeTo(closestEnemy)
                    if (range <= 1) {
                        defender.attack(closestEnemy)
                    } else if (defender.hits < defender.hitsMax || range <= 3) {
                        defender.heal(defender)
                    }

                    if (defender.hitsMax - defender.hits > 300) {
                        // 跑
                        const centerPos = new RoomPosition(room.memory.center[0], room.memory.center[1], room.name)
                        TowerController.allHeal(room.tower, defender)
                        if (defender.pos.getRangeTo(centerPos) > 4) {
                            goTo(defender, centerPos, { range: 4 })
                        }
                    } else {
                        // 追
                        defender.moveTo(closestEnemy)
                    }
                }

                // 可视化防御爬想去的位置
                if (pos) {
                    visual.line(defender.pos, pos, {
                        color: 'red',
                    })

                    visual.circle(defender.pos, {
                        radius: 0.15,
                        stroke: 'red',
                    })
                }
            })

            towerTargets.push(...groupEnemys.filter((e) => e.hits < e.hitsMax * 0.9))
        })

        if (towerTargets.length) {
            // 打血最少的敌人
            const target = towerTargets.reduce((target, creep) => (target.hits < creep.hits ? target : creep))
            TowerController.allAttack(room.tower, target)
        }
    }

    /**
     * 获取 memory
     */
    public static getMemory(room: Room) {
        if (!room.memory.defense) {
            room.memory.defense = {
                defender: [],
            }
        }
        return room.memory.defense
    }

    /**
     * 获取 defender
     */
    public static getDefender(room: MyRoom, creepNames: string[]) {
        return getCreeps(room, creepNames)
    }

    /**
     * 添加 defender 孵化任务
     */
    public static addDefenderSpawnTask(room: MyRoom, type: DefenderType, creepNames: string[]) {
        const storage = room.storage
        if (!storage || (storage.store['XUH2O'] < 3000 && storage.store['XKHO2'] < 3000)) return

        // 先看是否有 XZHO2 用于 boost，方便确定 move 比例
        let canBoostMove = false
        if (room.storage && room.storage.store['XZHO2'] > 1000) {
            canBoostMove = true
        }

        // 红球一体机为了保证威力必须 boost move
        if (type === 'red_green' && (room.energyCapacityAvailable < 5000 || !canBoostMove)) {
            type = 'red'
        }

        if (storage.store['XUH2O'] < 3000) {
            type = 'blue'
        }
        if (type === 'red_green' && (storage.store['XGHO2'] < 3000 || storage.store['XLHO2'] < 3000)) {
            type = 'red'
        }
        if (storage.store['XKHO2'] < 3000) {
            type = 'red'
        }

        const attackPart = type === 'blue' ? RANGED_ATTACK : ATTACK

        let body: BodyPartArrayType = [
            [attackPart, 40],
            [MOVE, 10],
        ]

        if (type === 'red' || type === 'blue') {
            if (!canBoostMove) {
                body = [
                    [attackPart, 33],
                    [MOVE, 17],
                ]
            }
        }

        if (type === 'red_green') {
            body = [
                [ATTACK, 2],
                [TOUGH, 4],
                [ATTACK, 28],
                [HEAL, 6],
                [MOVE, 10],
            ]
        }

        const task: SpawnTask = {
            role: CreepRoles.defender,
            body,
            defaultAdaption: true,
            memory: {
                taskId: type,
                boost: {
                    data: {
                        [attackPart]: {
                            type: type === 'blue' ? 'XKHO2' : 'XUH2O',
                            require: 50,
                            amount: 0,
                        },
                        ...(canBoostMove ? { [MOVE]: { type: 'XZHO2', require: 50, amount: 0 } } : {}),
                        ...(type === 'red_green'
                            ? {
                                  [TOUGH]: { type: 'XGHO2', require: 50, amount: 0 },
                                  [HEAL]: { type: 'XLHO2', require: 50, amount: 0 },
                              }
                            : {}),
                    },
                },
            },
        }

        const creepName = SpawnController.addSpawnTask(room, task)
        if (!creepName) return

        creepNames.push(creepName)
    }

    /**
     * 获取敌人分组，同时分配防御者
     */
    public static getEnemyGroup(room: MyRoom, defenders: Creep[], memory: DefenseMemory) {
        if (Game.time % 3 === 0) {
            delete DefenseTaskManager.enemyGroup[room.name]
        }

        if (DefenseTaskManager.enemyGroup[room.name]) {
            return DefenseTaskManager.enemyGroup[room.name]
        }

        const enemyGroups: EnemyGroup[] = []
        const enemys = [...(room.enemys || [])]
        const enemyMap = new Map<string, Creep>(enemys.map((creep) => [creep.name, creep]))
        const defenderMap = new Map<string, Creep>(defenders.map((creep) => [creep.name, creep]))
        const freeRampartPos = DefenseTaskManager.getFreeRampart(room)
        // 是否可以孵化防御者，已经有孵化任务或者站不下了就不孵化
        let canSpawn =
            SpawnController.searchSpawnTaskByRole(room, CreepRoles.defender).length <= 0 &&
            defenders.length < freeRampartPos.length

        // 计算敌人战力
        enemys.forEach((e) => {
            const { attack, rangeAttack, heal, work } = getCreepWarAbility(e)

            // 已经被分组
            if (!enemyMap.get(e.name)) return

            // 炮灰也不管，1500
            if (e.hitsMax < 3000 || attack + rangeAttack + heal / 0.3 + work < 1500) {
                enemyMap.delete(e.name)
                return
            }

            const group: EnemyGroup = {
                enemyNames: [e.name],
                defenders: [],
            }
            const groupEnemys: Creep[] = [e]
            enemyMap.delete(e.name)

            // 该爬周围一圈当作一个分组
            enemyMap.forEach((e2) => {
                if (!e2.pos.isNearTo(e)) return

                group.enemyNames.push(e2.name)
                groupEnemys.push(e2)
                enemyMap.delete(e2.name)
            })

            // 需要分配的防御者数量
            let needCount = group.enemyNames.length > 2 ? 2 : room.controller.level < 7 ? 2 : 1
            // 一体机不分配防御者
            if (groupEnemys.length === 1 && !attack && !work) {
                needCount = 0
            }

            while (needCount && defenderMap.size) {
                const groupCenter = DefenseTaskManager.calcEnemyGroupCenter(groupEnemys)
                // 找到最近的防御者
                const defender = findClosestObjByXY(groupCenter.x, groupCenter.y, [...defenderMap.values()])
                if (defender) {
                    group.defenders.push(defender.name)
                    defenderMap.delete(defender.name)
                }
                needCount--
            }
            // 如果防御者不够，就孵化
            if (needCount && canSpawn) {
                const creepName: string[] = []
                let type: DefenderType = 'red'
                if (group.enemyNames.length > 2) {
                    type = 'blue'
                }
                // else if (!groupEnemys.find((e) => e._warAbility.attack > 0)) {
                //     type = 'red_green'
                // }
                DefenseTaskManager.addDefenderSpawnTask(room, memory.type || type, creepName)
                if (creepName[0]) {
                    group.defenders.push(creepName[0])
                    memory.defender.push(creepName[0])
                }
                canSpawn = false
            }

            enemyGroups.push(group)
        })

        // 多余的防御者进行二次分配，暂时每个敌人分组多分配一个
        if (defenderMap.size) {
            for (const group of enemyGroups) {
                const defender = [...defenderMap.values()].pop()
                if (!defender) break
                group.defenders.push(defender.name)
                defenderMap.delete(defender.name)
            }
        }

        DefenseTaskManager.enemyGroup[room.name] = enemyGroups
        return enemyGroups
    }

    /**
     * 获取房间中空闲的边缘 rampart 的位置
     */
    public static getFreeRampart(room: MyRoom) {
        if (DefenseTaskManager.freeRampart[room.name]?.time < Game.time) {
            delete DefenseTaskManager.freeRampart[room.name]
        }

        if (DefenseTaskManager.freeRampart[room.name]?.pos) {
            return DefenseTaskManager.freeRampart[room.name].pos
        }

        const dangerArea = RampartController.getDangerArea(room)
        const freeRampart: RoomPosition[] = []
        room.rampart?.forEach(({ pos }) => {
            // 该位置不能靠近敌人
            if (dangerArea.get(pos.x, pos.y) != RoomDangerAreaType.rampart) return

            // rampart 下面有不能行走的建筑
            const structs = pos.lookFor(LOOK_STRUCTURES)
            if (structs.find((s) => !['road', 'container', 'rampart'].includes(s.structureType))) return

            freeRampart.push(pos)
        })

        DefenseTaskManager.freeRampart[room.name] = {
            time: Game.time + 63,
            pos: freeRampart,
        }

        return freeRampart
    }

    /**
     * 获取最近的未被占用的空闲 rampart 点位
     */
    public static getNearestFreeRampart(room: MyRoom, usedPosSet: Set<ZipPos>, targetPos: { x: number; y: number }) {
        const freeRampart = DefenseTaskManager.getFreeRampart(room).filter(
            (pos) => !usedPosSet.has(`${pos.x}/${pos.y}`),
        )
        if (!freeRampart.length) return

        const pos = findClosestPosByXY(targetPos.x, targetPos.y, freeRampart)
        if (!pos) return

        usedPosSet.add(`${pos.x}/${pos.y}`)
        return pos
    }

    /**
     * 计算敌人分组的中心点
     */
    public static calcEnemyGroupCenter(enemys: Creep[]) {
        const [x, y] = enemys.reduce(
            (sum, creep) => {
                sum[0] += creep.pos.x
                sum[1] += creep.pos.y
                return sum
            },
            [0, 0],
        )

        return {
            x: x / enemys.length,
            y: y / enemys.length,
        }
    }

    /**
     * 防御者移动
     */
    public static defenderMove(defender: Creep, target: RoomPosition) {
        if (defender.pos.isEqualTo(target)) return

        const dangerArea = RampartController.getDangerArea(defender.room as MyRoom)

        goTo(defender, target, {
            range: 0,
            maxRooms: 1,
            costsCallback: (roomName, costs) => {
                if (roomName !== defender.memory.belong) return costs

                dangerArea.forEach((x, y, type) => {
                    costs.set(x, y, Math.max(costs.get(x, y), RoomDangerAreaWeight[type]))
                })
                return costs
            },
        })
    }

    /**
     * 更新房间中的核弹信息
     */
    public static updateNuke(room: MyRoom) {
        if (Game.time % 3) return

        const nukes = room.find(FIND_NUKES)
        if (!nukes.length) return

        DefenseTaskManager.nukes[room.name] = nukes.map((nuke) => ({
            id: nuke.id,
            landTime: Game.time + nuke.timeToLand,
            pos: nuke.pos,
        }))
    }

    /**
     * 获取指定位置周围的核弹数量
     */
    public static getNukesByRange(room: MyRoom, pos: RoomPosition, range = 2) {
        if (!DefenseTaskManager.nukes[room.name]) return []

        return DefenseTaskManager.nukes[room.name].filter((nuke) => nuke.pos.getRangeTo(pos) <= range)
    }

    /**
     * 是否有即将落地的核弹
     *
     * @param room 房间
     * @param time 落地时间
     */
    public static hasNukeLand(room: MyRoom, time: number) {
        const nukes = DefenseTaskManager.nukes[room.name]
        if (!nukes) return false

        return nukes.some((nuke) => nuke.landTime <= time && nuke.landTime >= Game.time)
    }

    /**
     * 修改防御者类型
     */
    public static changeDefenderType(room: MyRoom, type: DefenderType) {
        if (!['red', 'blue', 'red_green'].includes(type)) return false

        const memory = DefenseTaskManager.getMemory(room)
        memory.type = type
        return true
    }
}

defineGlobalProperty('Defense', {
    value: DefenseTaskManager,
})
